/*
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 * openUBMC is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *         http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

#include <gtest/gtest.h>
#include <ipmb/ipmb.h>
#include <mc/engine/service.h>
#include <memory>

#include <imu/imu.h>

class test_imu : public dev::imu {
public:
    using dev::imu::imu;
    using dev::imu::parse_pmu_pci_response;
    using dev::imu::send_pmu_pci_request;

    bool get_pmu_status(dev::pmu_version_info& version_info) {
        version_info.device1      = 0x11;
        version_info.device2      = 0x22;
        version_info.pmu_version1 = 0x33;
        version_info.pmu_version2 = 0x44;
        return true;
    }

    std::optional<std::vector<uint8_t>>
    get_info_from_imu(bool is_local, dev::imu_pcie_device_req_t& pcie_device_info) {
        std::vector<uint8_t> mock_data = {0x34, 0x12, 0x78, 0x56};
        return mock_data;
    }

    bool send_ipmi_request(uint8_t* reqdata, uint32_t reqdata_len, std::string& rspdata) {
        rspdata = "mock_response";
        return true;
    }

    std::optional<std::vector<uint8_t>> send_pmu_pci_request(const dev::imu_pcie_info& pcie_info) {
        std::vector<uint8_t> mock_response = {0xDB, 0x07, 0x00, 0x34, 0x12, 0x78, 0x56};
        return mock_response;
    }
};

class imu_test : public ::testing::Test {
protected:
    void SetUp() override {
        m_service = std::make_unique<mc::engine::service>("bmc.kepler.test_imu");
        m_imu     = std::make_unique<test_imu>(m_service.get());
    }

    void TearDown() override {
        m_imu.reset();
        m_service.reset();
    }

    std::unique_ptr<mc::engine::service> m_service;
    std::unique_ptr<test_imu>            m_imu;
};

TEST_F(imu_test, constructor) {
    EXPECT_NE(m_imu, nullptr);
}

TEST_F(imu_test, destructor) {
    EXPECT_NO_THROW(m_imu.reset());
}

TEST_F(imu_test, get_pmu_status) {
    dev::pmu_version_info version_info;

    bool result = m_imu->get_pmu_status(version_info);

    EXPECT_TRUE(result);
    EXPECT_EQ(version_info.device1, 0x11);
    EXPECT_EQ(version_info.device2, 0x22);
    EXPECT_EQ(version_info.pmu_version1, 0x33);
    EXPECT_EQ(version_info.pmu_version2, 0x44);
}

TEST_F(imu_test, send_pmu_pci_request) {
    dev::imu_pcie_info pcie_info = {.is_local     = true,
                                    .cpu_id       = 0,
                                    .address      = dev::VID_DID_ADDRESS,
                                    .function_num = 0,
                                    .device_num   = 0,
                                    .bus_num      = 0,
                                    .read_length  = 4};

    auto result = m_imu->send_pmu_pci_request(pcie_info);

    EXPECT_TRUE(result.has_value());
    EXPECT_EQ(result->size(), 7);

    EXPECT_EQ((*result)[0], 0xDB);
    EXPECT_EQ((*result)[1], 0x07);
    EXPECT_EQ((*result)[2], 0x00);
    EXPECT_EQ((*result)[3], 0x34);
    EXPECT_EQ((*result)[4], 0x12);
    EXPECT_EQ((*result)[5], 0x78);
    EXPECT_EQ((*result)[6], 0x56);
}

TEST_F(imu_test, parse_pmu_pci_response_normal) {
    std::vector<uint8_t> response_data = {0xDB, 0x07, 0x00, 0x34, 0x12, 0x78, 0x56};

    std::vector<uint8_t> result = m_imu->parse_pmu_pci_response(response_data, 4);

    EXPECT_EQ(result.size(), 4);
    EXPECT_EQ(result[0], 0x34);
    EXPECT_EQ(result[1], 0x12);
    EXPECT_EQ(result[2], 0x78);
    EXPECT_EQ(result[3], 0x56);
}

TEST_F(imu_test, parse_pmu_pci_response_edge_cases) {
    std::vector<uint8_t> empty_data;
    EXPECT_TRUE(m_imu->parse_pmu_pci_response(empty_data, 4).empty());

    std::vector<uint8_t> short_data = {0xDB, 0x07, 0x00, 0x34, 0x12};
    auto                 result     = m_imu->parse_pmu_pci_response(short_data, 4);
    EXPECT_EQ(result.size(), 2);
    EXPECT_EQ(result[0], 0x34);
    EXPECT_EQ(result[1], 0x12);
}

TEST_F(imu_test, send_ipmi_request) {
    uint8_t     test_data[] = {0x01, 0x02, 0x03, 0x04};
    std::string response;

    bool result = m_imu->send_ipmi_request(test_data, sizeof(test_data), response);

    EXPECT_TRUE(result);
    EXPECT_EQ(response, "mock_response");
}

TEST_F(imu_test, get_info_from_imu) {
    dev::imu_pcie_device_req_t pcie_device_info = {.system_id        = 0,
                                                   .socket_id        = 0,
                                                   .bus              = 0,
                                                   .device           = 0,
                                                   .function         = 0,
                                                   .pci_info_address = dev::VID_DID_ADDRESS,
                                                   .rlen             = 4};

    auto result = m_imu->get_info_from_imu(true, pcie_device_info);

    EXPECT_TRUE(result.has_value());
    EXPECT_EQ(result->size(), 4);
    EXPECT_EQ((*result)[0], 0x34);
    EXPECT_EQ((*result)[1], 0x12);
    EXPECT_EQ((*result)[2], 0x78);
    EXPECT_EQ((*result)[3], 0x56);
}

TEST_F(imu_test, error_handling_null_service) {
    EXPECT_THROW(test_imu(nullptr), mc::invalid_argument_exception);
}

TEST_F(imu_test, error_handling_malformed_response) {
    std::vector<uint8_t> malformed_data = {0xAA, 0xBB, 0xCC, 0x34, 0x12};
    auto                 result         = m_imu->parse_pmu_pci_response(malformed_data, 4);

    EXPECT_EQ(result.size(), 2);
    EXPECT_EQ(result[0], 0x34);
    EXPECT_EQ(result[1], 0x12);
}

TEST_F(imu_test, error_handling_corrupted_response) {
    std::vector<uint8_t> corrupted_data = {0xDB, 0x07, 0x00, 0xFF, 0xFE, 0xFD};
    auto                 result         = m_imu->parse_pmu_pci_response(corrupted_data, 4);

    EXPECT_EQ(result.size(), 3);
    EXPECT_EQ(result[0], 0xFF);
    EXPECT_EQ(result[1], 0xFE);
    EXPECT_EQ(result[2], 0xFD);
}

TEST_F(imu_test, error_handling_boundary_values) {
    std::vector<uint8_t> large_data(300, 0xAA);
    large_data[0] = 0xDB;
    large_data[1] = 0x07;
    large_data[2] = 0x00;

    auto result = m_imu->parse_pmu_pci_response(large_data, 255);
    EXPECT_EQ(result.size(), 255);

    auto zero_result = m_imu->parse_pmu_pci_response(large_data, 0);
    EXPECT_EQ(zero_result.size(), 0);
}

TEST_F(imu_test, error_handling_concurrent_access) {
    std::vector<uint8_t> test_data = {0xDB, 0x07, 0x00, 0x11, 0x22, 0x33, 0x44};

    std::vector<std::thread>          threads;
    std::vector<std::vector<uint8_t>> results(10);

    for (int i = 0; i < 10; ++i) {
        threads.emplace_back([&, i]() {
            results[i] = m_imu->parse_pmu_pci_response(test_data, 4);
        });
    }

    for (auto& thread : threads) {
        thread.join();
    }

    for (const auto& result : results) {
        EXPECT_EQ(result.size(), 4);
        EXPECT_EQ(result[0], 0x11);
        EXPECT_EQ(result[1], 0x22);
        EXPECT_EQ(result[2], 0x33);
        EXPECT_EQ(result[3], 0x44);
    }
}

TEST_F(imu_test, error_handling_recovery) {
    std::vector<uint8_t> normal_data   = {0xDB, 0x07, 0x00, 0xAA, 0xBB, 0xCC, 0xDD};
    auto                 normal_result = m_imu->parse_pmu_pci_response(normal_data, 4);
    EXPECT_EQ(normal_result.size(), 4);

    std::vector<uint8_t> empty_data;
    auto                 empty_result = m_imu->parse_pmu_pci_response(empty_data, 4);
    EXPECT_TRUE(empty_result.empty());

    auto recovery_result = m_imu->parse_pmu_pci_response(normal_data, 4);
    EXPECT_EQ(recovery_result.size(), 4);
    EXPECT_EQ(recovery_result[0], 0xAA);
}
